/*
 * Copyright (C) 2019 Zilliqa
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <algorithm>
#include <thread>

#include "DirectoryService.h"
#include "libCrypto/Sha2.h"
#include "libData/AccountStore/AccountStore.h"
#include "libMediator/Mediator.h"
#include "libNode/Node.h"
#include "libUtils/BitVector.h"
#include "libUtils/DataConversion.h"
#include "libUtils/Logger.h"

using namespace std;
using namespace boost::multiprecision;

bool DirectoryService::VerifyMicroBlockCoSignature(
    const MicroBlock& microBlock) {
  LOG_MARKER();

  const vector<bool>& B2 = microBlock.GetB2();
  vector<PubKey> keys;
  unsigned int index = 0;
  unsigned int count = 0;

  if (m_mediator.m_DSCommittee->size() != B2.size()) {
    LOG_GENERAL(WARNING, "Mismatch: Shard(DS) size = "
                             << m_mediator.m_DSCommittee->size()
                             << ", co-sig bitmap size = " << B2.size());
    return false;
  }

  for (const auto& ds : *m_mediator.m_DSCommittee) {
    if (B2.at(index)) {
      keys.emplace_back(ds.first);
      count++;
    }
    index++;
  }

  if (count != ConsensusCommon::NumForConsensus(B2.size())) {
    LOG_GENERAL(WARNING, "Cosig was not generated by enough nodes");
    return false;
  }

  shared_ptr<PubKey> aggregatedKey = MultiSig::AggregatePubKeys(keys);
  if (aggregatedKey == nullptr) {
    LOG_GENERAL(WARNING, "Aggregated key generation failed");
    return false;
  }

  // Verify the collective signature
  zbytes message;
  if (!microBlock.GetHeader().Serialize(message, 0)) {
    LOG_GENERAL(WARNING, "MicroBlockHeader serialization failed");
    return false;
  }
  microBlock.GetCS1().Serialize(message, message.size());
  BitVector::SetBitVector(message, message.size(), microBlock.GetB1());
  if (!MultiSig::MultiSigVerify(message, 0, message.size(), microBlock.GetCS2(),
                                *aggregatedKey)) {
    LOG_GENERAL(WARNING, "Cosig verification failed");
    for (auto& kv : keys) {
      LOG_GENERAL(WARNING, kv);
    }
    return false;
  }

  return true;
}

bool DirectoryService::ProcessMicroblockSubmission(
    [[gnu::unused]] const zbytes& message, [[gnu::unused]] unsigned int offset,
    [[gnu::unused]] const Peer& from,
    [[gnu::unused]] const unsigned char& startByte,
    std::shared_ptr<zil::p2p::P2PServerConnection>) {
  LOG_MARKER();

  LOG_GENERAL(
      WARNING,
      "ProcessMicroblockSubmission shouldn't be called in desharded mode!");
  return false;
}
